//ual.v
`include "ual.h"
`include "sizes.h"

`define OUTADDER		2'b11
`define OUTGENERAL		2'b10
`define OUTMULTIPLY		2'b01
`define OUTDIVIDE		2'b00

module ual(cmd, in1, in2, out, z, n, p, c, ovr);
  input  [`UALCMDSZ] cmd;
  input  [`DATASIZE] in1, in2;
  reg    [1:0] outsource;
  wire   [`DATASIZE] outadd, outmul, quot, rem;
  wire   [`DATASIZE] outdiv = cmd[0] ? rem : quot;
  reg    [`DATASIZE] outgen, temp;
  output [`DATASIZE] out;
  wire   [`DATASIZE] out = outsource[1] ? (outsource[0] ? outadd : outgen) :
                                          (outsource[0] ? outmul : outdiv);
  output z, n, p, c, ovr;
  reg    c, cin;
  wire   z, n, p, cout, ovr; //la ovr trebuie mai mult scris! (?!?)
  reg    [4:0] i;
  
  adder    add0(in1, in2, outadd, cin, cout, ovr, cmd[`ADDERCMDSIZE]);
  multiply mpy0(in1, in2, outmul, ovr);
  divider  div0(in1, in2, quot, rem, ovr);
  
initial begin
  outgen = 0;  outsource = `OUTGENERAL;
  c = 0;  cin = 0;
end

assign z = ~(|out);
assign n = out[`DATABITS-1];
assign p = ^out;

always @(cmd or in1 or in2) begin
  case (cmd)
  `ZERO, `SUB, `ADD, `XOR, `OR, `AND, `MINUS1, `NEG: outsource = `OUTADDER;
  `SHR: begin
    if (in1 > `DATABITS) outgen = 0;
    else begin
      outgen = in2 >> in1;
//      outgen = {in1'b0, temp};
//      for (i = 0; i < in1; i = i + 1) //numarator cu incarcare [?!? -power]
//        outgen = {1'b0, outgen[`DATABITS-1:1]};
    end
    outsource = `OUTGENERAL;
  end
  `SAR: begin
    outgen = in2;
    for (i = 0; i < in1; i = i + 1) //numarator cu incarcare
      outgen = {in1[`DATABITS-1], outgen[`DATABITS-1:1]};
    outsource = `OUTGENERAL;
  end
  `SHL: begin
    if (in1 > `DATABITS) outgen = 0;
    else begin
      outgen = in2;
      for (i = 0; i < in1; i = i + 1) begin //numarator cu incarcare [shrug... -power]
        c = outgen[`DATABITS-1];
        outgen = {outgen[`DATABITS-2:0], 1'b0};
      end
    end
    outsource = `OUTGENERAL;
  end
  `RCR: begin
    outgen = in2;
    for (i = 0; i < in1; i = i + 1) begin //numarator cu incarcare
      c = outgen[0];
      outgen = {outgen[`DATABITS-2:0], 1'b0};
    end
    outsource = `OUTGENERAL;
  end
  `MUL: outsource = `OUTMULTIPLY;
  `DIV, `REM: outsource = `OUTDIVIDE;
  `ONE: begin outgen = 1; outsource = `OUTGENERAL; end
  `TWO: begin outgen = 2; outsource = `OUTGENERAL; end
  `THREE: begin outgen = 3; outsource = `OUTGENERAL; end
  `FOUR: begin outgen = 4; outsource = `OUTGENERAL; end
  `FIVE: begin outgen = 5; outsource = `OUTGENERAL; end
  `PASS: begin outgen = in1; outsource = `OUTGENERAL; end
  endcase
  #2; //stabilizare semnale
//  $display ("__UAL>  (%h) [Z=%h P=%h N=%h C=%h O=%h] (%h,%h)->%h", cmd, z, p, n, c, ovr, in1, in2, out);
end
endmodule


///////////////////////////////////////////////////////////////////////////////
//sumator pe 32 de bitzi descris dpdv functional
//compatibil cu 74F381 la nivel de comenzi
module adder(a, b, f, cin, cout, ovr, sel);
  input  [`DATASIZE] a, b;
  output [`DATASIZE] f;
  reg    [`DATASIZE] f;
  input  cin;
  output cout, ovr;
  reg    cout, ovr;
  input  [`ADDERCMDSIZE] sel;

initial begin  ovr = 0;  f = 0;  cout = 0; end
  
always @(sel or a or b) begin
  ovr = 0;
  case (sel)
  `CLEAR: begin f = 0;  cout = 1;  ovr = 1; end
  `BMINUSA: {cout, f} = b + ~a + 1;
  `AMINUSB: {cout, f} = a + ~b + 1;
  `APLUSB: {cout, f} = a + b + cin;
  `AXORB: f = a ^ b;
  `AORB: f = a | b;
  `AANDB: f = a & b;
  `PRESET: f = ~`DATABITS'b0;
  endcase
end
endmodule

///////////////////////////////////////////////////////////////////////////////
//divider
module divider(a, b, q, r, ovr);
  input  [`DATASIZE] a, b;
  output [`DATASIZE] q, r;
  reg    [`DATASIZE] q, r;
  output ovr;
  reg    ovr;

initial begin ovr = 0;  q = 0;  r = 0; end

always @(a or b) begin
  if (!b) begin
    r = 0;  q = 0;  ovr = 1;
  end
  else begin
    r = a % b;  q = a / b;
  end
end
endmodule

///////////////////////////////////////////////////////////////////////////////
//inmultitor
module multiply(i1, i2, out, ovr);
  input  [`DATASIZE] i1, i2;
  output [`DATASIZE] out;
  reg    [`DATASIZE] out, temp;
  output ovr;
  reg    ovr, c;
  integer i;

initial begin  out = 0;  ovr = 0; end

always @(i1 or i2) begin
  temp = i2;  out = i1[0] ? i2 : 0;
  for (i = 1; i < `DATABITS; i = i + 1) begin
    temp = temp << 1;
    if (i1[i]) out = out + temp;
  end
end
endmodule